"Implementation details for container_structure_test rule."

load("@aspect_bazel_lib//lib:paths.bzl", "BASH_RLOCATION_FUNCTION", "to_rlocation_path")
load("@aspect_bazel_lib//lib:windows_utils.bzl", "create_windows_native_launcher_script")

_attrs = {
    "image": attr.label(
        allow_files = True,
        doc = "Label of an oci_image or oci_tarball target.",
    ),
    "configs": attr.label_list(allow_files = True, mandatory = True),
    "driver": attr.string(
        default = "docker",
        # https://github.com/GoogleContainerTools/container-structure-test/blob/5e347b66fcd06325e3caac75ef7dc999f1a9b614/pkg/drivers/driver.go#L26-L28
        values = ["docker", "tar", "host"],
        doc = "See https://github.com/GoogleContainerTools/container-structure-test#running-file-tests-without-docker",
    ),
    "platform": attr.string(
        default = "linux/amd64",
        doc = "Set platform if host is multi-platform capable (default \"linux/amd64\")",
    ),
    "_runfiles": attr.label(default = "@bazel_tools//tools/bash/runfiles"),
    "_windows_constraint": attr.label(default = "@platforms//os:windows"),
}

CMD_HEAD = [
    "#!/usr/bin/env bash",
    "# This script generated by container_structure_test.bzl",
    BASH_RLOCATION_FUNCTION,
]

CMD = """\
readonly st=$(rlocation {st_path})
readonly jq=$(rlocation {jq_path})
readonly image=$(rlocation {image_path})

# When the image points to a folder, we can read the index.json file inside
if [[ -d "$image" ]]; then
  readonly DIGEST=$("$jq" -r '.manifests[0].digest | sub(":"; "-")' "$image/index.json")
  exec "$st" test --driver {driver} {fixed_args} --default-image-tag "cst.oci.local/$DIGEST:$DIGEST" $@
else
  exec "$st" test --driver {driver} {fixed_args} $@
fi
"""

def _structure_test_impl(ctx):
    fixed_args = []
    test_bin = ctx.toolchains["@container_structure_test//bazel:structure_test_toolchain_type"].st_info.binary
    jq_bin = ctx.toolchains["@aspect_bazel_lib//lib:jq_toolchain_type"].jqinfo.bin

    default_info = ctx.attr.image[DefaultInfo] if DefaultInfo in ctx.attr.image else None
    output_group_info = ctx.attr.image[OutputGroupInfo] if OutputGroupInfo in ctx.attr.image else None
    image = None
    image_files = []
    if output_group_info:
        for group_name in ["oci_layout", "oci_tarball"]:
            if group_name in output_group_info:
                image_files = output_group_info[group_name].to_list()
                if len(image_files) != 1:
                    fail("the 'image' attribute contains the '%s' output group but it does not have exactly one file" % group_name)
                image = image_files[0]
                break
    if not image and default_info and len(default_info.files.to_list()) == 1:
        image_files = default_info.files.to_list()
        image = image_files[0]
    if not image:
        fail("the 'image' attribute must be a target that produces exactly one file or contain either the 'oci_layout' or 'oci_tarball' output groups")

    image_path = to_rlocation_path(ctx, image)

    # Prefer to use a tarball if we are given one, as it works with more 'driver' types.
    if image_path.endswith(".tar"):
        fixed_args.extend(["--image", "$(rlocation %s)" % image_path])
    else:
        # https://github.com/GoogleContainerTools/container-structure-test/blob/5e347b66fcd06325e3caac75ef7dc999f1a9b614/cmd/container-structure-test/app/cmd/test.go#L110
        if ctx.attr.driver != "docker":
            fail("when the 'driver' attribute is not 'docker', then the image must be a .tar file")
        fixed_args.extend(["--ignore-ref-annotation", "--image-from-oci-layout", "$(rlocation %s)" % image_path])

    for arg in ctx.files.configs:
        fixed_args.extend(["--config", "$(rlocation %s)" % to_rlocation_path(ctx, arg)])

    if ctx.attr.platform:
        fixed_args.extend(["--platform", ctx.attr.platform])

    bash_launcher = ctx.actions.declare_file("%s.sh" % ctx.label.name)
    ctx.actions.write(
        bash_launcher,
        content = "\n".join(CMD_HEAD) + CMD.format(
            st_path = to_rlocation_path(ctx, test_bin),
            jq_path = to_rlocation_path(ctx, jq_bin),
            driver = ctx.attr.driver,
            image_path = image_path,
            fixed_args = " ".join(fixed_args),
        ),
        is_executable = True,
    )

    is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo])
    launcher = create_windows_native_launcher_script(ctx, bash_launcher) if is_windows else bash_launcher
    runfiles = ctx.runfiles(
        files = image_files + ctx.files.configs + [
            bash_launcher,
            test_bin,
            jq_bin,
        ],
    ).merge(ctx.attr._runfiles.default_runfiles)

    return DefaultInfo(runfiles = runfiles, executable = launcher)

lib = struct(
    attrs = _attrs,
    implementation = _structure_test_impl,
)
